/*++

Copyright (c) Microsoft Corporation. All rights reserved. 

You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt).
If you do not agree to the terms, do not use the code.


Module Name:

    lock.c

Abstract:

    This module contains the code to implement the NtLockFile and the
    NtUnlockFile system services for the NT I/O system.

--*/

#include "iomgr.h"

#pragma alloc_text(PAGE, NtLockFile)
#pragma alloc_text(PAGE, NtUnlockFile)

NTSTATUS
NtLockFile(
    __in HANDLE FileHandle,
    __in_opt HANDLE Event,
    __in_opt PIO_APC_ROUTINE ApcRoutine,
    __in_opt PVOID ApcContext,
    __out PIO_STATUS_BLOCK IoStatusBlock,
    __in PLARGE_INTEGER ByteOffset,
    __in PLARGE_INTEGER Length,
    __in ULONG Key,
    __in BOOLEAN FailImmediately,
    __in BOOLEAN ExclusiveLock
    )

/*++

Routine Description:

    This service locks a specified range of bytes on the file specified by
    the FileHandle parameter.  The lock may either be an exclusive lock or
    a shared lock.  Furthermore, the caller has the option of specifying
    whether or not the service should return immediately if the lock cannot
    be acquired without waiting.

Arguments:

    FileHandle - Supplies a handle to an open file.

    Event - Supplies an optional event to be set to the Signaled state when
        the operation is complete.

    ApcRoutine - Supplies an optional APC routine to be executed when the
        operation is complete.

    ApcContext - Supplies a context parameter to be passed to the ApcRoutine,
        if an ApcRoutine was specified.

    IoStatusBlock - Address of the caller's I/O status block.

    ByteOffset - Specifies the starting byte offset of the range to lock.

    Length - Specifies the length of the byte range to be locked.

    Key - Specifies the key to be associated with the lock.

    FailImmediately - Specifies that if the lock cannot immediately be
        acquired that the service should return to the caller.

    ExclusiveLock - Specifies, if TRUE, that the lock should be an exclusive
        lock;  otherwise the lock is a shared lock.

Return Value:

    The status returned is success if the operation was properly queued to
    the I/O system.  Once the operation completes, the status can be
    determined by examining the Status field of the I/O status block.

--*/

{
    PIRP irp;
    NTSTATUS status;
    PFILE_OBJECT fileObject;
    PDEVICE_OBJECT deviceObject;
    PFAST_IO_DISPATCH fastIoDispatch;
    PKEVENT eventObject = (PKEVENT) NULL;
    KPROCESSOR_MODE requestorMode;
    PIO_STACK_LOCATION irpSp;
    LARGE_INTEGER fileOffset;
    LARGE_INTEGER length;
    ACCESS_MASK grantedAccess;
    OBJECT_HANDLE_INFORMATION handleInformation;
    BOOLEAN synchronousIo;
    PETHREAD CurrentThread;

    PAGED_CODE();

    //
    // Get the previous mode;  i.e., the mode of the caller.
    //

    CurrentThread = PsGetCurrentThread ();
    requestorMode = KeGetPreviousModeByThread(&CurrentThread->Tcb);

    //
    // Reference the file object so the target device can be found and the
    // access rights mask can be used in the following checks for callers
    // in user mode.  Note that if the handle does not refer to a file
    // object, then it will fail.
    //

    status = ObReferenceObjectByHandle( FileHandle,
                                        0L,
                                        IoFileObjectType,
                                        requestorMode,
                                        (PVOID *) &fileObject,
                                        &handleInformation);
    if (!NT_SUCCESS( status )) {
        return status;
    }

    grantedAccess = handleInformation.GrantedAccess;

    if (requestorMode != KernelMode) {

        //
        // The caller's access mode is not kernel so probe each of the arguments
        // and capture them as necessary.  If any failures occur, the condition
        // handler will be invoked to handle them.  It will simply cleanup and
        // return an access violation status code back to the system service
        // dispatcher.
        //

        //
        // Check to ensure that the caller has either READ or WRITE access to
        // the file.  If not, cleanup and return an error.
        //

        if (!SeComputeGrantedAccesses( grantedAccess, FILE_READ_DATA | FILE_WRITE_DATA )) {
            ObDereferenceObject( fileObject );
            return STATUS_ACCESS_DENIED;
        }

        try {

            //
            // The IoStatusBlock parameter must be writeable by the caller.
            //

            ProbeForWriteIoStatus(IoStatusBlock);

            //
            // If this is a 32-bit asynchronous IO, then mark the Iosb being sent as so.
            // Note: IopMarkApcRoutineIfAsyncronousIo32 must be called after probing
            //       the IoStatusBlock structure for write.
            //

            IopMarkApcRoutineIfAsyncronousIo32(IoStatusBlock,ApcRoutine,(fileObject->Flags & FO_SYNCHRONOUS_IO));

            //
            // The ByteOffset parameter must be readable by the caller.  Probe
            // and capture it.
            //

            ProbeForReadSmallStructure( ByteOffset,
                                        sizeof( LARGE_INTEGER ),
                                        sizeof( ULONG ) );
            fileOffset = *ByteOffset;

            //
            // Likewise, the Length parameter must also be readable by the
            // caller.  Probe and capture it as well.
            //

            ProbeForReadSmallStructure( Length,
                                        sizeof( LARGE_INTEGER ),
                                        sizeof( ULONG ) );
            length = *Length;

            //
            // If this file has an I/O completion port associated w/it, then
            // ensure that the caller did not supply an APC routine, as the
            // two are mutually exclusive methods for I/O completion
            // notification.
            //

            if (fileObject->CompletionContext && IopApcRoutinePresent( ApcRoutine )) {
                ObDereferenceObject( fileObject );
                return STATUS_INVALID_PARAMETER;
            }

        } except(EXCEPTION_EXECUTE_HANDLER) {

            //
            // An exception was incurred attempting to probe the caller's
            // parameters.  Dereference the file object and return an
            // appropriate error status code.
            //

            ObDereferenceObject( fileObject );
            return GetExceptionCode();
        }

    } else {

        //
        // The caller's mode was kernel.  Get the ByteOffset and Length
        // parameter 's to the expected locations.
        //

        fileOffset = *ByteOffset;
        length = *Length;
    }

    //
    // Get the address of the event object and set the event to the Not-
    // Signaled state, if an event was specified.  Note here, too, that if
    // the handle does not refer to an event, or if the event cannot be
    // written, then the reference will fail.  Since certain legacy
    // applications rely on an old bug in Win32's LockFileEx, we must
    // tolerate bad event handles.
    //

    if (ARGUMENT_PRESENT( Event )) {
        status = ObReferenceObjectByHandle( Event,
                                            EVENT_MODIFY_STATE,
                                            ExEventObjectType,
                                            requestorMode,
                                            (PVOID *) &eventObject,
                                            NULL );
        if (!NT_SUCCESS( status )) {
            ASSERT( !eventObject );
        } else {
            KeClearEvent( eventObject );
        }
    }

    //
    // Get the address of the target device object and the fast Io dispatch
    // structure.
    //

    deviceObject = IoGetRelatedDeviceObject( fileObject );
    fastIoDispatch = deviceObject->DriverObject->FastIoDispatch;

    //
    // Turbo lock support.  If the fast Io Dispatch specifies a fast lock
    // routine then we'll first try and calling it with the specified lock
    // parameters.
    //

    if (fastIoDispatch && fastIoDispatch->FastIoLock) {

        IO_STATUS_BLOCK localIoStatus;

        if (fastIoDispatch->FastIoLock( fileObject,
                                        &fileOffset,
                                        &length,
                                        PsGetCurrentProcessByThread(CurrentThread),
                                        Key,
                                        FailImmediately,
                                        ExclusiveLock,
                                        &localIoStatus,
                                        deviceObject )) {

            //
            // Carefully return the I/O status.
            //

            try {
#if defined(_WIN64)
                //
                // If this is a32-bit thread, and the IO request is 
                // asynchronous, then the IOSB is 32-bit. Wow64 always sends
                // the 32-bit IOSB when the I/O is asynchronous.
                //
                if (IopIsIosb32(ApcRoutine)) {
                    PIO_STATUS_BLOCK32 UserIosb32 = (PIO_STATUS_BLOCK32)IoStatusBlock;
                    
                    UserIosb32->Information = (ULONG)localIoStatus.Information;
                    UserIosb32->Status = (NTSTATUS)localIoStatus.Status;
                } else {
                    *IoStatusBlock = localIoStatus;
                }
#else
                *IoStatusBlock = localIoStatus;
#endif
            } except( EXCEPTION_EXECUTE_HANDLER ) {
                localIoStatus.Status = GetExceptionCode();
                localIoStatus.Information = 0;
            }

            //
            // If a valid event was specified, set it.
            //

            if (eventObject) {
                KeSetEvent( eventObject, 0, FALSE );
                ObDereferenceObject( eventObject );
            }

            //
            // Note that the file object event need not be set to the
            // Signaled state, as it is already set.
            //

            //
            // If this file object has a completion port associated with it
            // and this request has a non-NULL APC context then a completion
            // message needs to be queued.
            //

            if (fileObject->CompletionContext && ARGUMENT_PRESENT( ApcContext )) {
                if (!NT_SUCCESS(IoSetIoCompletion( fileObject->CompletionContext->Port,
                                                   fileObject->CompletionContext->Key,
                                                   ApcContext,
                                                   localIoStatus.Status,
                                                   localIoStatus.Information,
                                                   TRUE ))) {
                    localIoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
                }
            }

            //
            // Cleanup and return.
            //

            fileObject->LockOperation = TRUE;
            ObDereferenceObject( fileObject );
            return localIoStatus.Status;
        }
    }

    //
    // Make a special check here to determine whether this is a synchronous
    // I/O operation.  If it is, then wait here until the file is owned by
    // the current thread.
    //

    if (fileObject->Flags & FO_SYNCHRONOUS_IO) {

        BOOLEAN interrupted;

        if (!IopAcquireFastLock( fileObject )) {
            status = IopAcquireFileObjectLock( fileObject,
                                               requestorMode,
                                               (BOOLEAN) ((fileObject->Flags & FO_ALERTABLE_IO) != 0),
                                               &interrupted );
            if (interrupted) {
                if (eventObject) {
                    ObDereferenceObject( eventObject );
                }
                ObDereferenceObject( fileObject );
                return status;
            }
        }
        synchronousIo = TRUE;
    } else {
        synchronousIo = FALSE;
    }

    //
    // Set the file object to the Not-Signaled state and mark it as having had
    // a lock operation performed on it.
    //

    KeClearEvent( &fileObject->Event );
    fileObject->LockOperation = TRUE;

    //
    // Allocate and initialize the I/O Request Packet (IRP) for this operation.
    // The allocation is performed with an exception handler in case the
    // caller does not have enough quota to allocate the packet.

    irp = IoAllocateIrp( deviceObject->StackSize, !synchronousIo );
    if (!irp) {

        //
        // An IRP could not be allocated.  Cleanup and return an appropriate
        // error status code.
        //

        IopAllocateIrpCleanup( fileObject, eventObject );

        return STATUS_INSUFFICIENT_RESOURCES;
    }
    irp->Tail.Overlay.OriginalFileObject = fileObject;
    irp->Tail.Overlay.Thread = CurrentThread;
    irp->RequestorMode = requestorMode;

    //
    // Fill in the service independent parameters in the IRP.
    //

    irp->UserEvent = eventObject;
    irp->UserIosb = IoStatusBlock;
    irp->Overlay.AsynchronousParameters.UserApcRoutine = ApcRoutine;
    irp->Overlay.AsynchronousParameters.UserApcContext = ApcContext;

    //
    // Get a pointer to the stack location for the first driver.  This will be
    // used to pass the original function codes and parameters.
    //

    irpSp = IoGetNextIrpStackLocation( irp );
    irpSp->MajorFunction = IRP_MJ_LOCK_CONTROL;
    irpSp->MinorFunction = IRP_MN_LOCK;
    irpSp->FileObject = fileObject;

    //
    // Copy the caller's parameters to the service-specific portion of the
    // IRP.
    //

    irpSp->Flags = 0;
    if (FailImmediately) {
        irpSp->Flags = SL_FAIL_IMMEDIATELY;
    }
    if (ExclusiveLock) {
        irpSp->Flags |= SL_EXCLUSIVE_LOCK;
    }
    irpSp->Parameters.LockControl.Key = Key;
    irpSp->Parameters.LockControl.ByteOffset = fileOffset;

    try {
        PLARGE_INTEGER lengthBuffer;

        //
        // Attempt to allocate an intermediary buffer to hold the length of
        // this lock operation.  If it fails, either because there is no
        // more quota, or because there are no more resources, then the
        // exception handler will be invoked to cleanup and exit.
        //

        lengthBuffer = ExAllocatePoolWithQuota( NonPagedPool,
                                                sizeof( LARGE_INTEGER ) );

        *lengthBuffer = length;
        irp->Tail.Overlay.AuxiliaryBuffer = (PCHAR) lengthBuffer;
        irpSp->Parameters.LockControl.Length = lengthBuffer;
    } except(EXCEPTION_EXECUTE_HANDLER) {

        //
        // An exception was incurred.  Simply clean everything up and
        // return an appropriate error status code.
        //

        IopExceptionCleanup( fileObject,
                             irp,
                             eventObject,
                             (PKEVENT) NULL );

        return GetExceptionCode();
    }

    //
    // Queue the packet, call the driver, and synchronize appropriately with
    // I/O completion.
    //

    return IopSynchronousServiceTail( deviceObject,
                                      irp,
                                      fileObject,
                                      FALSE,
                                      requestorMode,
                                      synchronousIo,
                                      OtherTransfer );
}

NTSTATUS
NtUnlockFile (
    __in HANDLE FileHandle,
    __out PIO_STATUS_BLOCK IoStatusBlock,
    __in PLARGE_INTEGER ByteOffset,
    __in PLARGE_INTEGER Length,
    __in ULONG Key
    )

/*++

Routine Description:

    This service releases the lock associated with the specified byte range
    for the file specified by the FileHandle parameter.

Arguments:

    FileHandle - Supplies a handle to an open file.

    IoStatusBlock - Address of the caller's I/O status block.

    ByteOffset - Specifies the byte offset of the range to unlock.

    Length - Specifies the length of the byte range to unlock.

    Key - Specifies the key associated with the locked range.

Return Value:

    The status returned is the final completion status of the operation.

--*/

{
    PIRP irp;
    NTSTATUS status;
    PFILE_OBJECT fileObject;
    PDEVICE_OBJECT deviceObject;
    PFAST_IO_DISPATCH fastIoDispatch;
    PKEVENT event;
    KPROCESSOR_MODE requestorMode;
    PIO_STACK_LOCATION irpSp;
    IO_STATUS_BLOCK localIoStatus;
    LARGE_INTEGER fileOffset;
    LARGE_INTEGER length;
    ACCESS_MASK grantedAccess;
    OBJECT_HANDLE_INFORMATION handleInformation;
    BOOLEAN synchronousIo;
    PETHREAD CurrentThread;

    PAGED_CODE();

    //
    // Get the previous mode;  i.e., the mode of the caller.
    //

    CurrentThread = PsGetCurrentThread ();
    requestorMode = KeGetPreviousModeByThread(&CurrentThread->Tcb);

    //
    // Reference the file object so the target device can be found and the
    // access rights mask can be used in the following checks for callers
    // in user mode.  Note that if the handle does not refer to a file
    // object, then it will fail.
    //

    status = ObReferenceObjectByHandle( FileHandle,
                                        0L,
                                        IoFileObjectType,
                                        requestorMode,
                                        (PVOID *) &fileObject,
                                        &handleInformation);
    if (!NT_SUCCESS( status )) {
        return status;
    }

    grantedAccess = handleInformation.GrantedAccess;

    //
    // Check to see if the requestor mode was user.  If so, perform a bunch
    // of extra checks.
    //

    if (requestorMode != KernelMode) {

        //
        // The caller's access mode is not kernel so probe each of the arguments
        // and capture them as necessary.  If any failures occur, the condition
        // handler will be invoked to handle them.  It will simply cleanup and
        // return an access violation status code back to the system service
        // dispatcher.
        //

        //
        // Check to ensure that the caller has either READ or WRITE access
        // to the file.  If not, cleanup and return an error.
        //

        if (!SeComputeGrantedAccesses( grantedAccess, FILE_READ_DATA | FILE_WRITE_DATA )) {
            ObDereferenceObject( fileObject );
            return STATUS_ACCESS_DENIED;
        }

        try {

            //
            // The IoStatusBlock parameter must be writeable by the caller.
            //

            ProbeForWriteIoStatus( IoStatusBlock );

            //
            // The ByteOffset parameter must be readable by the caller.  Probe
            // and capture it.
            //

            ProbeForReadSmallStructure( ByteOffset,
                                        sizeof( LARGE_INTEGER ),
                                        sizeof( ULONG ) );
            fileOffset = *ByteOffset;

            //
            // Likewise, the Length parameter must also be readable by the
            // caller.  Probe and capture it as well.
            //

            ProbeForReadSmallStructure( Length,
                                        sizeof( LARGE_INTEGER ),
                                        sizeof( ULONG ) );
            length = *Length;

        } except(EXCEPTION_EXECUTE_HANDLER) {

            //
            // An exception was incurred while attempting to probe the
            // caller's parameters.  Dereference the file object and return
            // an appropriate error status code.
            //

            ObDereferenceObject( fileObject );
            return GetExceptionCode();

        }

    } else {

        //
        // The caller's mode was kernel.  Get the ByteOffset and Length
        // parameter 's to the expected locations.
        //

        fileOffset = *ByteOffset;
        length = *Length;
    }

    //
    // Get the address of the target device object.  If this file represents
    // a device that was opened directly, then simply use the device or its
    // attached device(s) directly.  Also get the fast I/O dispatch address.
    //

    if (!(fileObject->Flags & FO_DIRECT_DEVICE_OPEN)) {
        deviceObject = IoGetRelatedDeviceObject( fileObject );
    } else {
        deviceObject = IoGetAttachedDevice( fileObject->DeviceObject );
    }
    fastIoDispatch = deviceObject->DriverObject->FastIoDispatch;

    //
    // Turbo lock support.  If the fast Io Dispatch specifies a fast lock
    // routine then we'll first try and calling it with the specified lock
    // parameters.
    //

    if (fastIoDispatch && fastIoDispatch->FastIoUnlockSingle) {

        IO_STATUS_BLOCK localIoStatus;

        if (fastIoDispatch->FastIoUnlockSingle( fileObject,
                                                &fileOffset,
                                                &length,
                                                PsGetCurrentProcessByThread(CurrentThread),
                                                Key,
                                                &localIoStatus,
                                                deviceObject )) {

            //
            // Carefully return the I/O status.
            //

            try {
                *IoStatusBlock = localIoStatus;
            } except( EXCEPTION_EXECUTE_HANDLER ) {
                localIoStatus.Status = GetExceptionCode();
                localIoStatus.Information = 0;
            }

            //
            // Cleanup and return.
            //

            ObDereferenceObject( fileObject );
            return localIoStatus.Status;
        }
    }

    //
    // Make a special check here to determine whether this is a synchronous
    // I/O operation.  If it is, then wait here until the file is owned by
    // the current thread.  If this is not a (serialized) synchronous I/O
    // operation, then allocate and initialize the local event.
    //

    if (fileObject->Flags & FO_SYNCHRONOUS_IO) {

        BOOLEAN interrupted;

        if (!IopAcquireFastLock( fileObject )) {
            status = IopAcquireFileObjectLock( fileObject,
                                               requestorMode,
                                               (BOOLEAN) ((fileObject->Flags & FO_ALERTABLE_IO) != 0),
                                               &interrupted );
            if (interrupted) {
                ObDereferenceObject( fileObject );
                return status;
            }
        }
        synchronousIo = TRUE;
        event = NULL;
    } else {

        //
        // This is a synchronous API being invoked for a file that is opened
        // for asynchronous I/O.  This means that this system service is
        // to synchronize the completion of the operation before returning
        // to the caller.  A local event is used to do this.
        //

        event = ExAllocatePool( NonPagedPool, sizeof( KEVENT ) );
        if (event == NULL) {
            ObDereferenceObject( fileObject );
            return STATUS_INSUFFICIENT_RESOURCES;
        }
        KeInitializeEvent( event, SynchronizationEvent, FALSE );
        synchronousIo = FALSE;
    }

    //
    // Set the file object to the Not-Signaled state.
    //

    KeClearEvent( &fileObject->Event );

    //
    // Allocate and initialize the I/O Request Packet (IRP) for this operation.
    // The allocation is performed with an exception handler in case the
    // caller does not have enough quota to allocate the packet.
    //

    irp = IoAllocateIrp( deviceObject->StackSize, FALSE );
    if (!irp) {

        //
        // An IRP could not be allocated.  Cleanup and return an appropriate
        // error status code.
        //

        if (event) {
            ExFreePool( event );
        }

        IopAllocateIrpCleanup( fileObject, (PKEVENT) NULL );

        return STATUS_INSUFFICIENT_RESOURCES;
    }
    irp->Tail.Overlay.OriginalFileObject = fileObject;
    irp->Tail.Overlay.Thread = CurrentThread;
    irp->RequestorMode = requestorMode;

    //
    // Fill in the service independent parameters in the IRP.
    //

    if (synchronousIo) {
        irp->UserEvent = (PKEVENT) NULL;
        irp->UserIosb = IoStatusBlock;
    } else {
        irp->UserEvent = event;
        irp->UserIosb = &localIoStatus;
        irp->Flags = IRP_SYNCHRONOUS_API;
    }
    irp->Overlay.AsynchronousParameters.UserApcRoutine = (PIO_APC_ROUTINE) NULL;

    //
    // Get a pointer to the stack location for the first driver.  This will
    // be used to pass the original function codes and parameters.
    //

    irpSp = IoGetNextIrpStackLocation( irp );
    irpSp->MajorFunction = IRP_MJ_LOCK_CONTROL;
    irpSp->MinorFunction = IRP_MN_UNLOCK_SINGLE;
    irpSp->FileObject = fileObject;

    try {
        PLARGE_INTEGER lengthBuffer;

        //
        // Attempt to allocate an intermediary buffer to hold the length of
        // this lock operation.  If it fails, either because there is no
        // more quota, or because there are no more resources, then the
        // exception handler will be invoked to cleanup and exit.
        //

        lengthBuffer = ExAllocatePoolWithQuota( NonPagedPool,
                                                sizeof( LARGE_INTEGER ) );

        *lengthBuffer = length;
        irp->Tail.Overlay.AuxiliaryBuffer = (PCHAR) lengthBuffer;
        irpSp->Parameters.LockControl.Length = lengthBuffer;
    } except(EXCEPTION_EXECUTE_HANDLER) {

        //
        // An exception was incurred.  Simply clean everything up and
        // return an appropriate error status code.
        //

        if (!(fileObject->Flags & FO_SYNCHRONOUS_IO)) {
            ExFreePool( event );
        }
  
        IopExceptionCleanup( fileObject,
                             irp,
                             NULL,
                             (PKEVENT) NULL );

        return GetExceptionCode();
    }

    //
    // Copy the caller's parameters to the service-specific portion of the
    // IRP.
    //

    irpSp->Parameters.LockControl.Key = Key;
    irpSp->Parameters.LockControl.ByteOffset = fileOffset;

    //
    // Queue the packet, call the driver, and synchronize appropriately with
    // I/O completion.
    //

    status = IopSynchronousServiceTail( deviceObject,
                                        irp,
                                        fileObject,
                                        FALSE,
                                        requestorMode,
                                        synchronousIo,
                                        OtherTransfer );

    //
    // If the file for this operation was not opened for synchronous I/O, then
    // synchronization of completion of the I/O operation has not yet occurred
    // since the allocated event must be used for synchronous APIs on files
    // opened for asynchronous I/O.  Synchronize the completion of the I/O
    // operation now.
    //

    if (!synchronousIo) {

        status = IopSynchronousApiServiceTail( status,
                                               event,
                                               irp,
                                               requestorMode,
                                               &localIoStatus,
                                               IoStatusBlock );
    }

    return status;
}

